#include "AppliedWarp.h"
#include <cmath>
#include <algorithm>
#include "ReadableImage.h"

using namespace std;

AppliedWarp::AppliedWarp(const ReadableImage& input)
    : Warp(),
      input_(&input),
      output_width_(0),
      output_height_(0),
      output_radius_(0.0) {
  updateOutputSpecs();
}

AppliedWarp::AppliedWarp(const Warp& warp, const ReadableImage& input)
    : Warp(warp),
      input_(&input),
      output_width_(0),
      output_height_(0),
      output_radius_(0.0) {
  updateOutputSpecs();
}

AppliedWarp::AppliedWarp(const AppliedWarp& rhs)
    : Warp(rhs),
      input_(rhs.input_),
      output_width_(rhs.output_width_),
      output_height_(rhs.output_height_),
      output_radius_(rhs.output_radius_) {
  updateOutputSpecs();
}

AppliedWarp& AppliedWarp::operator=(const AppliedWarp& rhs) {
  input_ = rhs.input_;
  output_width_ = rhs.output_width_;
  output_height_ = rhs.output_height_;
  output_radius_ = rhs.output_radius_;
  return *this;
}

void AppliedWarp::updateOutputSpecs() {
  if (scaleMode() == Warp::CUSTOM_SIZE) {
    // Use custom size warp.
    output_width_ = customWidth();
    output_height_ = customHeight();
    // Find output radius from size and crop setting.
    if (cropMode() == Warp::CROP) {
      // Output radius is distance to corner.
      output_radius_ = sqrt(output_width_ * output_width_ +
                            output_height_ * output_height_) / 2;
    } else if (cropMode() == Warp::FIT_ALL) {
      // Output radius is distance to nearest edge.
      output_radius_ = min(output_width_, output_height_) / 2;
    }
  } else {
    // Find output radius from pixel zoom.
    int w = input_->width();
    int h = input_->height();
    if (scaleMode() == Warp::AT_LEAST_UNITY) {
      output_radius_ = (exp(2 * M_PI / w * h) - 1) / (exp(2 * M_PI / w) - 1);
    } else if (scaleMode() == Warp::AT_MOST_UNITY) {
      output_radius_ = (1 - exp(-2 * M_PI / w * h)) / (1 - exp(-2 * M_PI / w));
    }
    // Find output size from radius and crop setting.
    if (cropMode() == Warp::CROP) {
      // Radius is distance to corner.
      output_width_ = 2 * int(floor(output_radius_ / sqrt(2)));
    } else {
      // Radius is distance to edge.
      output_width_ = 2 * int(floor(output_radius_));
    }
    // Image is square.
    output_height_ = output_width_;
  }
}

